﻿using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows.Threading;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using ValidationResult = System.ComponentModel.DataAnnotations.ValidationResult;

namespace AmazonGlacierGUI.UI.Infrastructure.MVVMFramework
{
    //9-march-2011 Priyan R
    public abstract class AppViewModelBase : ViewModelBase, IDataErrorInfo
    {

        private List<PropertyValidation> _validations = new List<PropertyValidation>();
        private List<ValidationResultData> _errors =
            new List<ValidationResultData>();
        private string _viewTitle = "";
        private List<Control> _uiBindingErrors = new List<Control>();
        public event EventHandler OnRequestClose;
        private Dispatcher _dispatcher;
        public bool IsCloseRequested { get; private set; }
        private bool _disablePropertyChangeValidate;
        protected AppViewModelBase()
        {
            EnableValidtion = false;
            PropertyChanged += (s, e) =>
            {
                if (e.PropertyName != "HasErrors" && EnableValidtion && _disablePropertyChangeValidate==false)
                    ValidateProperty(e.PropertyName);
            };                        
            AddAllAttributeValidators();
        }
        public void SetDispacther(Dispatcher dispatcher)
        {
            _dispatcher = dispatcher;
        }
        public void ExecuteOnUI(Action action)
        {
            _dispatcher.Invoke(action);
        }
        public virtual string OnClose()
        {
            return null;
        }
        #region Validation
        public string Error
        {
            get
            {
                var errors = GetAllErrors();
                if (errors.Count == 0) return null;
                return String.Join("\n", errors.Select(p => p.ErrorMessage));
            }
        }
        public string this[string columnName]
        {
            get
            {
                if (EnableValidtion == false) return null;
                var errors = GetErrors(columnName).ToList();
                if (errors.Count == 0)
                    return null;
                return errors.First().ErrorMessage;
            }
        }

        public void AddUIBindingError(Control sourceControl)
        {
            if (!_uiBindingErrors.Contains(sourceControl))
                _uiBindingErrors.Add(sourceControl);
        }
        public void RemoveUIBindingError(Control sourceControl)
        {
            if (_uiBindingErrors.Contains(sourceControl))
                _uiBindingErrors.Remove(sourceControl);
        }
        public void ClearUIBindingErrors()
        {
            _uiBindingErrors.Clear();
        }
        public bool HasUIBindingErrors
        {
            get
            {
                return _uiBindingErrors.Count > 0;
            }
        }
        public bool ValidateGroups(params string[] groups)
        {
            return ValidateGroups(true,groups);
        }
        bool ValidateGroups(bool raiseEvent,params string[] groups)
        {
            ClearAllErrors();
            if (groups != null)
            {
                _validations.Where(p => p.ValidationGroups != null && UtilityHelper.IsValueExistInCollection
                    (p.ValidationGroups, groups)).ToList().ForEach(PerformValidation);
            }
            else
            {
                _validations.ToList().ForEach(PerformValidation);
            }
            NotifyErrorsChanged();
            return !HasErrors;
        }

        public bool ValidateAll()
        {
            var v=ValidateGroups(false,null);;            
            return v;
        }
        public bool ValidateProperty(string property)
        {
            var propertyValidations = _validations.Where(p => p.PropertyName == property)
                .ToList();
            if (propertyValidations.Count == 0) return true;
            ClearError(property, false);
            propertyValidations.ForEach(PerformValidation);
            NotifyErrorsChanged();                  
            return !_errors.Where(p => p.PropertyName == property).Any();

        }

        public bool ValidateProperty<T>(Expression<Func<T>> propertyExpression)
        {
           return ValidateProperty(ExtractPropertyName(propertyExpression));
        }
        private void PerformValidation(PropertyValidation validation)
        {
            var result = validation.ValidationMethod();
            result.PropertyName = validation.PropertyName;
            if (result != ValidationResultData.Success)
            {
                AddErrorMessage(result, false);
            }
        }
        public List<ValidationResultData> GetErrors(string propertyName)
        {
            return _errors.Where(p => p.PropertyName == propertyName).ToList();
        }
        public List<ValidationResultData> GetAllErrors()
        {
            return _errors;
        }
        public void ClearAllErrors()
        {
            _errors.Clear();
            NotifyErrorsChanged();
        }
        
        public void ClearError(string property)
        {
            ClearError(property, true);
        }
        private void ClearError(string property, bool raiseEvent)
        {
            while (_errors.Where(p => p.PropertyName == property).Any())
            {
                _errors.Remove(_errors.Where(p => p.PropertyName == property).First());
            }
            if (raiseEvent)
            {
                NotifyErrorsChanged();
            }
        }
        public void ClearError<T>(Expression<Func<T>> propertyExpression)
        {
            ClearError(ExtractPropertyName(propertyExpression));
        }

        public void AddValidation<T>(Expression<Func<T>> propertyExpression,
            Func<ValidationResultData> validationMethod)
        {
            AddValidation(ExtractPropertyName(propertyExpression), validationMethod);
        }

        public void AddValidation(string property,Func<ValidationResultData> validationMethod)
        {
            AddValidation(property,null,validationMethod);
        }
        public void AddValidation(string property,string displayName,Func<ValidationResultData> validationMethod)
        {
            string[] groups = null;
            if (property != null)
            {
                var propInfo = this.GetType().GetProperty(property);
                var groupAttribute = (PropertyValidationGroupAttribute) propInfo.
                                                                            GetCustomAttributes(
                                                                                typeof (PropertyValidationGroupAttribute
                                                                                    ), false).FirstOrDefault();
                if (groupAttribute != null)
                {
                    groups = groupAttribute.ValidationsGroups;
                }
            }
            AddValidation(property,displayName,groups,validationMethod);
        }
        public void AddValidation(string property,string displayName,string[] groups,Func<ValidationResultData> validationMethod)
        {          
            var validation = new PropertyValidation(property,displayName,groups, validationMethod);
            _validations.Add(validation);
        }


        private void AddErrorMessage(ValidationResultData error, bool raiseEvent)
        {
            _errors.Add(error);
            if (raiseEvent)
            {
                NotifyErrorsChanged();
            }
        }
        public void AddErrorMessage<T>(Expression<Func<T>> propertyExpression, ValidationResultData error)
        {
            error.PropertyName = ExtractPropertyName(propertyExpression);
            AddErrorMessage(error, true);
        }
        void NotifyErrorsChanged()
        {
            RaisePropertyChanged(() => HasErrors);
            _disablePropertyChangeValidate = true;
            foreach (var prop in GetAllErrors().Where(p=>p.PropertyName!=null).Select(p=>p.PropertyName))
            {
                RaisePropertyChanged(prop);
            }
            _disablePropertyChangeValidate = false;
        }
        protected void AddAllAttributeValidators()
        {
            PropertyInfo[] propertyInfos = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                Attribute[] custom = Attribute.GetCustomAttributes(propertyInfo, typeof(ValidationAttribute), true);
                foreach (var attribute in custom)
                {
                    var property = propertyInfo;
                    var validationAttribute = attribute as ValidationAttribute;

                    if (validationAttribute == null)
                        throw new NotSupportedException("validationAttribute variable should be inherited from ValidationAttribute type");

                    string displayName = property.Name;
                    var displayAttribute = Attribute.GetCustomAttributes(propertyInfo, typeof(DisplayAttribute)).FirstOrDefault() as DisplayAttribute;
                    if (displayAttribute != null)
                    {
                        displayName = displayAttribute.GetName();
                    }                    
                    AddValidation(propertyInfo.Name,displayName,
                                  () =>
                                  {
                                      var value = property.GetGetMethod().Invoke(this, new object[] { });
                                      var message = validationAttribute.FormatErrorMessage(displayName);
                                      var result = validationAttribute.GetValidationResult(value,
                                                                                           new ValidationContext(
                                                                                               this, null, null)
                                                                                           {
                                                                                               MemberName =
                                                                                                   property.Name
                                                                                           });
                                      return (result == ValidationResult.Success)
                                          ? ValidationResultData.Success
                                          : new ValidationResultData(message);

                                  });


                }
            }
        }

        #endregion
        public virtual string ViewTitle
        {

            get
            {
                return _viewTitle;
            }
            set
            {
                _viewTitle = value;
                RaisePropertyChanged(() => ViewTitle);
            }
        }


        public virtual bool HasErrors
        {
            get
            {

                return _errors.Count > 0;
            }

        }



        public new void RaisePropertyChanged(string propertyName)
        {

            base.RaisePropertyChanged(propertyName);
        }
        public void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            RaisePropertyChanged(ExtractPropertyName(propertyExpression));



        }
        public string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
        {
            if (propertyExpression == null)
            {
                throw new ArgumentNullException("propertyExpression");
            }

            var memberExpression = propertyExpression.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new ArgumentException("propertyExpression");
            }

            var property = memberExpression.Member as PropertyInfo;
            if (property == null)
            {
                throw new ArgumentException("propertyExpression");
            }

            //if (!property.DeclaringType.IsAssignableFrom(GetType()))
            //{
            //    throw new ArgumentException("propertyExpression");
            //}

            var getMethod = property.GetGetMethod(true);
            if (getMethod == null)
            {
                // this shouldn't happen - the expression would reject the property before reaching this far
                throw new ArgumentException("propertyExpression");
            }

            if (getMethod.IsStatic)
            {
                throw new ArgumentException("propertyExpression");
            }

            return memberExpression.Member.Name;
        }
        public virtual void OnInit()
        {
            
        }
        public void Close()
        {
            IsCloseRequested = true;
            OnRequestClose(this,null);
        }
        protected virtual void OnCloseCommand()
        {
            Close();    
        }        
        private RelayCommand _closeCommand;
        public virtual RelayCommand CloseCommand
        {
            get
            {
                if (_closeCommand == null)
                {
                    _closeCommand = new RelayCommand(OnCloseCommand);
                }
                return _closeCommand;
            }       
        }

        public bool EnableValidtion { get; set; }
     
        #region helpers
        #endregion
    }

}
